#include<Lcode/l_main.h>
#include<machine/m_bw.h>
#include"../share/predicat.h"

#define NUM_ADDR_OPD_FOR_MEM_OP 2
#define BW_RT_REGISTER L_MAC_P10
int L_ignore_oper (L_Oper * op)
{
  /* Ignore the following opcodes */
  if ((op->opc == Lop_PROLOGUE) ||
      (op->opc == Lop_EPILOGUE) ||
      (op->opc == Lop_PRED_CLEAR) ||
      (op->opc == Lop_PRED_SET) || (op->opc == Lop_DEFINE))
    return (1);

  return (0);
}

static Set get_all_def(int reg_mac_index)
{
	return HashTable_find_or_null((PF_default_flow)->hash_RD_operand_def,reg_mac_index);
}


typedef struct _HL_element{
	struct _HL_element * next;
	struct _HL_element * prev;
	struct _HL_element * serial;
	void * data;
}* HL_element;
	
typedef  struct _Hash_List{

	int (* get_key)(void * data);

	unsigned int hash_size;//always is the power of 2
	unsigned int hash_mask;
	struct _HL_element** hash_table;
	struct _HL_element* current;
	L_Alloc_Pool * element_pool;
	
	HL_element first;
	HL_element last;

}* Hash_List ;


/*******************	function for mem pool *******************************/
L_Alloc_Pool * _HL_alloc_pool = NULL;

void init_hash_list_pool()
{
	_HL_alloc_pool = L_create_alloc_pool("_HL_element", sizeof(struct _HL_element),64);
}

void clean_hash_list_pool()
{
	L_free_alloc_pool(_HL_alloc_pool);
}



Hash_List  HL_init( int (* data_get_key)(void * ptr))
{
	Hash_List hlist = (Hash_List)malloc(sizeof(struct _Hash_List));
	hlist->get_key = data_get_key; 
	hlist->hash_size = 1<<8;	
	hlist->hash_mask = hlist->hash_size - 1;
	hlist->hash_table = malloc(sizeof(HL_element) * hlist->hash_size);
	int i =0; 
	for( i = 0; i < hlist->hash_size; i++)
	{
		hlist->hash_table[i] = NULL;
	}
	hlist->element_pool =  	_HL_alloc_pool;
	hlist->current = NULL;
	hlist->first = NULL;
	hlist->last = NULL;
	return hlist;
}

void HL_free(Hash_List hlist)
{
	int i = 0; 
	HL_element e;	
	HL_element tmp;
	for(i = 0; i < hlist->hash_size; i++)
	{
		e = hlist->hash_table[i] ;
		while(e)
		{
			tmp = e->serial;	
			L_free(_HL_alloc_pool, e);		
			e = tmp;
		}
	}
	free(hlist->hash_table);
	hlist->current = NULL;
	hlist->first = NULL;
	hlist->last = NULL;
}

void * HL_find(Hash_List hlist , void * data)
{
	int key = hlist->get_key(data);
	int index = key & hlist->hash_mask;
	HL_element e = hlist->hash_table[index];
	while( e != NULL)
	{
		if( hlist->get_key(e->data) == key)
		{
			return e->data;
		}
		e = e->serial;
	}
	return NULL;
}

void HL_start(Hash_List hlist)
{
	if(hlist == NULL)
	{
		L_punt("HL_start: the hlist is empty");
	}
	hlist->current = NULL;
}


void * HL_get_next(Hash_List hlist)
{

	if(hlist->first ==  NULL)
	{//if empty hlist ui
		return NULL;
	}
	if(hlist->current == NULL)
	{
		hlist->current = hlist->first;
		return hlist->current->data;
	}
	hlist->current = hlist->current->next;
	if( hlist->current == NULL)
	{
		return NULL;
	}
	else
	{
		return hlist->current->data;
	}
}

Hash_List HL_insert_last(Hash_List hlist, void * ptr )
{
	if( hlist == NULL)
	{
		L_punt("HL_insert_last : the hlist is not initialize");
	}
	int key = hlist->get_key(ptr);
	int index = hlist->hash_mask & key;
	HL_element element = NULL;
	//insert hash_table
	if( hlist->hash_table[index] == NULL)
	{
		element= L_alloc(hlist->element_pool);
		element->serial = NULL;
		element->next= NULL;
		element->prev = NULL;
		element->data = ptr;
		hlist->hash_table[index] = element;
	}
	else
	{
		HL_element e = hlist->hash_table[index];
		if( key ==  hlist->get_key(e->data))
			return hlist;
		while( e->serial != NULL)
		{
			e = e->serial;
			if(key == hlist->get_key(e->data))
				return hlist;
		}	
		element= L_alloc(hlist->element_pool);
		element->serial = NULL;
		element->next= NULL;
		element->prev = NULL;
		element->data = ptr;
		e->serial = element;
	}

	//insert list;
	if(hlist->last == NULL)
	{
		hlist->last = element;
		hlist->first = hlist->last;
	}
	else
	{
		hlist->last->next = element;
		element->prev = hlist->last;
		hlist->last = element;
	}
	return hlist;
}
	
int oper_get_key(void *l_oper)
{
	L_Oper * oper = (L_Oper * ) l_oper;
	return oper->id;
}	

/**************************************end  hash_list *********************************/

static Hash_List oper_list = NULL;
static Hash_List terminater_list = NULL;

static Set new_oper_set = NULL;

void mark_new_oper(L_Oper * oper)
{
	//L_Attr * attr = L_new_attr("bw_opti_new",0);
	//oper->attr = L_concat_attr(attr, oper->attr);	
	Set_add(new_oper_set, oper->id);
}

int  is_new_oper(L_Oper * oper)
{
	if( Set_in(new_oper_set, oper->id))
		return 1;
	else
		return 0;
}


L_Oper * bw_build_src_trans_oper(L_Cb * cb, L_Oper * oper,  int i, int ctype)
{
	L_Operand*new_operand = L_new_register_operand(++(L_fn->max_reg_id),ctype, L_PTYPE_NULL);
	L_Oper * new_oper = L_create_new_op(Lop_MOV);
	new_oper->src[0] = oper->src[i];//old operand
	new_oper->dest[0] = new_operand;	
	oper->src[i] = L_copy_operand(new_operand);
	L_insert_oper_before(cb, oper, new_oper);
	mark_new_oper(new_oper);
	return new_oper;
}

L_Oper * bw_build_dest_trans_oper(L_Cb * cb, L_Oper * oper, int i, int ctype )
{
	L_Operand * new_operand = L_new_register_operand(++(L_fn->max_reg_id),ctype, L_PTYPE_NULL);
	L_Oper * new_oper = L_create_new_op(Lop_MOV);
	new_oper->src[0] = new_operand;
	new_oper->dest[0] = oper->dest[i];
	oper->dest[i] = L_copy_operand(new_oper->src[0]);
	L_insert_oper_after(cb, oper, new_oper);
	mark_new_oper( new_oper);
	return new_oper;
}

//convert all pointer register to the general register 
static void init_any_reg_of_fn(L_Func * l_fn)
{
	L_Cb * cb = NULL;
	L_Oper * oper = NULL;
	int i = 0;
	for( cb = l_fn->first_cb; cb != NULL; cb = cb->next_cb)
	{
		for( oper = cb->first_op; oper != NULL; oper = oper->next_op)
		{
			for( i = 0; i < L_max_dest_operand; i++)
			{
				if( bw_is_gen_addr_reg(oper->dest[i]))
				{
					 oper->dest[i]->ctype = L_CTYPE_INT;
				}
				if( bw_is_mac_addr_reg(oper->dest[i]))
				{
					oper->dest[i]->ctype = L_CTYPE_POINTER;
				}
				if( bw_is_mac_data_reg(oper->dest[i]))
				{
					oper->dest[i]->ctype = L_CTYPE_INT;
				}
			}

			for( i = 0; i < L_max_src_operand; i++)
			{
				if( bw_is_gen_addr_reg(oper->src[i]))
				{
					 oper->src[i]->ctype = L_CTYPE_INT;
				}
				if( bw_is_mac_addr_reg(oper->src[i]))
					oper->src[i]->ctype = L_CTYPE_POINTER;
				if( bw_is_mac_data_reg(oper->src[i]))
					oper->src[i]->ctype = L_CTYPE_INT;
			}
		}
	}
}

void bw_addr_init(L_Func * l_fn)
{
   L_do_flow_analysis (l_fn,/*LIVE_VARIABLE |*/ REACHING_DEFINITION/*|DOMINATOR|POST_DOMINATOR*/);
                    //  AVAILABLE_EXPRESSION|REACHING_DEFINITION|DOMINATOR);
	init_hash_list_pool();

	oper_list = HL_init(oper_get_key);
	terminater_list = HL_init(oper_get_key);

	new_oper_set = Set_new(); 
	
	init_any_reg_of_fn(l_fn);
}
/***********************************************end common func*********************************/

static void simple_distinguish_addr_reg(L_Func * fn)
{
	L_Cb * cb;
	L_Oper * oper;

	int i = 0;
	for( cb = fn->first_cb; cb!= NULL; cb = cb->next_cb)
	{
		for( oper = cb->first_op; oper != NULL; oper = oper->next_op)
		{
			if( is_new_oper(oper))
				continue;
			if( oper->opc == Lop_DEFINE)
				continue;
			if( L_general_load_opcode(oper) || L_general_store_opcode(oper))
			{
				for( i = 0; i < NUM_ADDR_OPD_FOR_MEM_OP ; i++)	
				{
					if(!L_is_register(oper->src[i]))
						continue;
					if(!bw_is_addr_reg(oper->src[i]) )
					{
						bw_build_src_trans_oper(cb, oper, i , L_CTYPE_POINTER);	
					}
				}
				for(i ; i < L_max_src_operand; i++)
				{
					if(!L_is_register(oper->src[i]))
						continue;
					if(!bw_is_addr_reg(oper->src[i]))
					{
						bw_build_src_trans_oper(cb, oper, i, L_CTYPE_INT);
					}
				}
			}
			else
			for( i = 0; i < L_max_src_operand; i++)
			{
				if( !L_is_variable(oper->src[i]))
					continue;
			
				if(bw_is_addr_reg(oper->src[i]))
				{
					bw_build_src_trans_oper(cb, oper, i, L_CTYPE_INT);
				}
			}
			if( bw_is_addr_reg(oper->dest[0]))
			{
				if( oper->opc != Lop_MOV)
				{
					bw_build_dest_trans_oper(cb, oper, 0, L_CTYPE_INT);
				}
			}
		}
	}
}


void L_simple_distinguish_addr(L_Func * fn)
{
	bw_addr_init(fn);
	init_any_reg_of_fn(fn);	
	simple_distinguish_addr_reg(fn);
}


/***************** optimization the addr_reg usage ********************************/
typedef struct _mac_associate_info{
	Set rd_set;
	L_Operand * operand;
} mac_associate_info;

static mac_associate_info ** mac_associate;

void init_mac_associate()
{
	int i;
	mac_associate = (mac_associate_info**)malloc( sizeof( mac_associate_info *) *(L_MAC_LAST ));
	for( i = 0; i < L_MAC_LAST; i++)
	{
		mac_associate[i] = NULL;
	}
}

void submit_mac_associate( L_Func * fn , Hash_List  oper_list)
{
	int i;
	L_Operand * new_operand;
	L_Oper * new_oper, * oper;
	L_Cb * cb;
	//change all the  src_operand that is mac data reg in oper which is in oper_list
	HL_start(oper_list);
	for( oper = HL_get_next(oper_list); oper != NULL; oper = HL_get_next(oper_list)) 
	{
		int src_operand_num = L_max_src_operand;
		if( L_general_load_opcode(oper) || L_general_store_opcode(oper))
		{	
			src_operand_num = NUM_ADDR_OPD_FOR_MEM_OP;
		}
		for( i = 0; i < src_operand_num ; i++) 
		{
			if( bw_is_mac_data_reg(oper->src[i]))
			{
				new_operand = L_copy_operand(mac_associate[oper->src[i]->value.mac]->operand);
				L_delete_operand(oper->src[i]);	
				oper->src[i]= new_operand; 
			}
		}
	}
	//change all the oper that  dest_operand 

	for( i = 0; i < L_MAC_LAST; i++)
	{
		int k = 0;
		if( mac_associate[i] != NULL)
		{
			int size = Set_size(mac_associate[i]->rd_set);
			int * buf = (int * ) malloc(size * sizeof(int));
			Set_2array(mac_associate[i]->rd_set, buf);
			for( k = 0; k < size; k++ )
			{
				L_Oper * dop = L_oper_hash_tbl_find_oper(fn->oper_hash_tbl,buf[k]);
				L_Cb *dcb = L_oper_hash_tbl_find_cb( fn->oper_hash_tbl, buf[k]);	
				if( HL_find(oper_list, dop)&& !L_general_load_opcode(dop))
				{
					//using the new_operand subtitud the old_dest_operand
					L_Operand * mac_operand = dop->dest[0]; 
					dop->dest[0] = L_copy_operand(mac_associate[i]->operand); 
					L_Oper * new_oper = L_create_new_op(Lop_MOV);
					new_oper->src[0] = L_copy_operand(mac_associate[i]->operand);
					new_oper->dest[0] = mac_operand;
					L_insert_oper_after(dcb, dop, new_oper); 
					mark_new_oper(new_oper);
				}
				else
				{
					// insert the new_oper that trans old_dest_operand to new_operand
					L_Oper * new_oper = L_create_new_op(Lop_MOV);
					new_oper->dest[0] = L_copy_operand(mac_associate[i]->operand);
					new_oper->src[0] = L_copy_operand(dop->dest[0]);
					L_insert_oper_after(dcb, dop, new_oper);
					mark_new_oper(new_oper);
				}
			}
			free(buf);
		}
	}		
//free all the mac_associate data struct

	for( i = 0; i < L_MAC_LAST; i++)
	{
		if( mac_associate[i])
		{
			L_delete_operand(mac_associate[i]->operand);
			Set_dispose(mac_associate[i]->rd_set);
			free(mac_associate[i]);
			mac_associate[i] = NULL;
		}
	}
	free(mac_associate);
	mac_associate = NULL;
}



void L_optimize_addr_reg(L_Func * l_fn)
{
	bw_addr_init(l_fn);
	L_Cb * cb;
	L_Oper * oper;
	int i = 0; 
	int index_op = 0;
	int num_src_operand = 0;
	//init 
	Set reg_set = NULL;
	reg_set = Set_new();

	cb = l_fn->first_cb;
	for(; cb != NULL; cb = cb->next_cb)
	{
		for(oper = cb->first_op; oper!= NULL; oper = oper->next_op)
		{
			//three kind of oper is the seed of oper_list
			if(L_general_store_opcode(oper) ||L_general_load_opcode(oper)) 
			{
				HL_insert_last(oper_list,oper);
			}
		}
	}
	HL_start(oper_list);
	// append the oper_list
	for(oper = HL_get_next(oper_list); oper != NULL; oper = HL_get_next(oper_list))
	{
		num_src_operand = L_max_src_operand;
		if(L_general_store_opcode(oper) || L_general_load_opcode(oper))
		{
			num_src_operand = NUM_ADDR_OPD_FOR_MEM_OP;
		}
		else
		{
			//the mac_addr is add into the reg_set;
			for(i = 0; i < L_max_dest_operand; i++)
			{
				if(L_is_reg(oper->dest[i]))
					Set_add(reg_set, L_REG_INDEX(oper->dest[i]->value.r));
				if(L_is_macro(oper->dest[i]))
					Set_add(reg_set, L_MAC_INDEX(oper->dest[i]->value.r));
			}
		}
			
		for(i = 0; i < num_src_operand ; i++)
		{
			if(L_is_reg(oper->src[i]))
			{
				Set_add(reg_set, L_REG_INDEX(oper->src[i]->value.r));
			}
			if(L_is_macro(oper->src[i]))
			{
				Set_add(reg_set, L_MAC_INDEX(oper->src[i]->value.r));
			}
		}

		//insert new oper that is  in the RD set of current src-operand and  is supported by AGU and 
		for(i = 0; i < num_src_operand; i++)
		{
			if(!(L_is_reg(oper->src[i])|| L_is_macro(oper->src[i])))
				continue;
			Set defs = L_get_oper_RIN_defining_opers(oper,oper->src[i]);
			int size = Set_size(defs);
			int * buf =(int *) malloc(sizeof(int) * size);
			Set_2array(defs, buf);
			for(index_op = 0; index_op < size; index_op++)
			{
				int op_id = buf[index_op];
				L_Oper * dop = L_oper_hash_tbl_find_oper(l_fn->oper_hash_tbl,op_id); 
				//for the macro register , it is not supported !
				if(is_supported_by_AGU(dop))
				{
					HL_insert_last(oper_list,dop);
				}
				else
					HL_insert_last(terminater_list, dop);
			}
			Set_dispose(defs);
			free(buf);
		}//for
	}//for
	
	
	/*process the mcro regiter
	after the process : no macro operand in the oper that is in oper_list*/	
	L_Operand * macro_src_operand = NULL;
	HL_start( oper_list);
	init_mac_associate();
	for(oper = HL_get_next( oper_list ); oper != NULL; oper = HL_get_next( oper_list))
	{
		//assume that the macro operand must be appeared in the oper_list as the src_operand
		num_src_operand = L_general_store_opcode(oper) ? NUM_ADDR_OPD_FOR_MEM_OP : L_max_src_operand;
		for(i = 0; i < num_src_operand ; i++)
		{
			if( bw_is_mac_data_reg(oper->src[i]))
			{
				int mac = oper->src[i]->value.mac; 
				if( mac_associate[mac] == NULL)
				{
					mac_associate[mac] = (mac_associate_info * )malloc(sizeof( mac_associate_info)); 
					mac_associate[mac]->operand = L_new_register_operand(++(L_fn->max_reg_id), L_CTYPE_POINTER, L_PTYPE_NULL);
					mac_associate[mac]->rd_set = NULL;
				}
				Set xdefs = L_get_oper_RIN_defining_opers(oper, oper->src[i]);
				mac_associate[mac]->rd_set = Set_union_acc(mac_associate[mac]->rd_set, xdefs);
			}//if
		}
	}

	submit_mac_associate(l_fn, oper_list);


/*process each oper in oper list ;
	1> for general register whose ctype is pointer , do nothing
	1> for general register  , just change the ctype;	
	2> for macor register (error , because the macro is already process  )
*/
	HL_start(oper_list);	
	for(oper = HL_get_next(oper_list) ; oper != NULL; oper = HL_get_next(oper_list))
	{
		num_src_operand = L_general_store_opcode(oper) ? NUM_ADDR_OPD_FOR_MEM_OP : L_max_src_operand;
		for( i = 0; i < num_src_operand; i++)
		{
			if(!L_is_register(oper->src[i]))
				continue;
			if(bw_is_gen_data_reg(oper->src[i]) )
			{
				if(Set_in( reg_set, L_REG_INDEX(oper->src[i]->value.r)))
				{
					//convert the general reg to addr_reg
					oper->src[i]->ctype = L_CTYPE_POINTER;	
				}
				else
				{
					//sanity check
					L_punt("error, expected  operand %d is in reg_set", i);
				}
			}
			else if(bw_is_mac_data_reg(oper->src[i]))
			{
				L_punt("error, the macro register is expected to be processed already");
			}
		}
		//not process the load oper's dest operand
		if( L_general_load_opcode(oper) )
			continue;
		for(i = 0; i < L_max_dest_operand ; i++)
		{
			if(!L_is_register(oper->dest[i]))
				continue;
			if(bw_is_gen_data_reg(oper->dest[i])) 
			{
				if(Set_in(reg_set, L_REG_INDEX(oper->dest[i]->value.r)))
				{
					oper->dest[i]->ctype = L_CTYPE_POINTER;	
				}
				else
				{
					L_punt("error, the gen_data_reg is expected to be in reg_set  already");
				}
			}
			else if(bw_is_mac_data_reg(oper->dest[i]))
			{
				L_punt("error, the macro reg is not expected be in the  oper_list ");
			}
		} 
	}//for oper_list


/*iterate each oper in function ,process the all general register operand  whose value is in the  reg_set ,but is still not addr register . we assume the macro register has already been  processed 
	for general register operand 
		for src operand 
			a.>create new general register operand  
			b.>subtitude the old operand with new general register operand  
			c.>insert transport oper before original oper  
		for dest operand 
			a.>create new general register operand 
			b.>create new oper transport new general register to original pointer reg	
			c.>insert new oper after  the original oper 
*/
	//first process the dest_operand 
	for(cb = l_fn->first_cb; cb != NULL; cb = cb->next_cb)
	{
		for(oper = cb->first_op; oper != NULL; oper = oper->next_op)
		{
			if( is_new_oper(oper) || (HL_find(oper_list, oper) &&!L_general_load_opcode(oper)))
				continue;
			for(i = 0; i < L_max_dest_operand; i++)
			{
				//process the mac_addr_reg
				if( bw_is_mac_addr_reg(oper->dest[i]) && !L_ignore_oper(oper))
				{
					bw_build_dest_trans_oper(cb, oper, i, L_CTYPE_INT);
				}
				if(bw_is_gen_data_reg(oper->dest[i]) &&
					Set_in(reg_set, L_REG_INDEX(oper->dest[i]->value.r)))
				{
					// 	oper: new_operand =  old_op .....
					//new_oper: old_operand = move new_operand
					oper->dest[i]->ctype = L_CTYPE_POINTER;
					bw_build_dest_trans_oper(cb, oper, i, L_CTYPE_INT);
				}
			}
			if( L_general_load_opcode(oper))	
				continue;
			for(i = 0; i < L_max_src_operand ; i++)
			{
				if(bw_is_mac_addr_reg(oper->src[i]) && !L_ignore_oper(oper))
				{
					bw_build_src_trans_oper( cb, oper, i, L_CTYPE_INT);
				}
				if(bw_is_gen_data_reg(oper->src[i]) && 
					Set_in(reg_set, L_REG_INDEX(oper->src[i]->value.r)))
				{
					//insert trans instruction
					oper->src[i]->ctype = L_CTYPE_POINTER;
					bw_build_src_trans_oper(cb, oper, i , L_CTYPE_INT);
				}
			}
		}
	}

	//never forget  release the memory!!!
	HL_free(oper_list);
	HL_free(terminater_list);
	Set_dispose(new_oper_set);
	clean_hash_list_pool();
	Set_dispose(reg_set );
}
		


/**************************** for optimizate mul_accumulate operation ***********************/

static void L_assign_macc_num(L_Func *fn,L_Oper *oper,int count)
{
	
}


void L_assign_macc(L_Func * fn)
{
}



void L_assign_acc(L_Func * fn)
{
}
	


	
